#ifdef  RUN_ENDBG
#define DBG(...)		printf( __VA_ARGS__ )
#else
#define DBG(...)
#endif

/* exec, fork */
#include <unistd.h>

/* shmem */
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

/* errno */
#include <errno.h>

/* waitpid */
#include <sys/wait.h>

/* data_t */
#include "bitonic_interface.h"

/* strtol */
#include <stdlib.h>

#include <string.h>
#include <stdio.h>


#define DEFAULT_TEST_LEN		1024
#define DEFAULT_TEST_LEN_STRING	"1024"
#define SHM_NAME	"/bitonic_par_data"


struct options {
	size_t test_l;
	char *test_l_s;
	size_t r; /* Repetitions */
	uint8_t run;
	char *p;
};

void print_array(data_t *a, size_t l) {
	printf("\n");
	for(size_t i = 0; i < l; i++)
		printf("  %x  ", a[i]);
	printf("\n");
	for(size_t i = 0;i < l; i++)
		printf(" [%ld] ", i);
	printf("\n");
}

void usage(int argc, char *argv[]) {
	printf(
"Usage %s [-l len]\n\
-l : length of test data. Default 1024\n\
-r : repetitions. Default is 1\n\
-[ocs] : runs either Openmp, Cilk or Sequential. These are exclusive.\n\
-p : the number of processors. Required to be a power of 2.\n\
", argv[0]);
}

void create_input(data_t *in, size_t l) {
	for(size_t i = 0; i < l/2; i++) {
		in[i] = i;
		in[i+(l/2)] = i;
	}

	if(l%2 == 1) {
		in[l-1] = in[l-2]+1;
	}
}


int main(int argc, char * argv[]) {
	struct options o;
	int mem_des = -1;
	int opt;
	data_t *data_in;

	o.r = 1;
	o.test_l = 0;
	o.run = 0;
	o.p = "4";

	while((opt = getopt(argc, argv, "l:r:ocsp:")) != -1) {
		switch(opt) {
			case 'p' : {
				o.p = optarg;
				break;
			}
			case 'o' : {
				o.run = 1;
				break;
			}
			case 'c' : {
				o.run = 2;
				break;
			}
			case 's' : {
				o.run = 0;
				break;
			}
			case 'r' : {
				o.r = (size_t) strtol(optarg, NULL, 10);
				break;
			}
			case 'l' : {
				o.test_l = (size_t) strtol(optarg, NULL, 10);
				o.test_l_s = optarg;
				DBG("Test size: %ld (%s)\n", o.test_l, optarg);
				break;
			}
			default : {
				usage(argc, argv);
				return 1;
			}
		}
	}

	if(o.test_l == 0) {
		o.test_l = DEFAULT_TEST_LEN;
		o.test_l_s = DEFAULT_TEST_LEN_STRING;
	}

	mem_des = shm_open(SHM_NAME,
		O_RDWR | O_CREAT,
		S_IRWXU);
	if(mem_des == -1) {
		printf("%s\n",
			strerror(errno));
		return 1;
	}

	if(0 > ftruncate(mem_des, o.test_l*sizeof(data_t))) {
		printf("%s\n",
			strerror(errno));
		return 1;
	}

	data_in = mmap(NULL,
		o.test_l*sizeof(data_t),
		PROT_READ | PROT_WRITE,
		MAP_SHARED,
		mem_des,
		0);
	if(MAP_FAILED == data_in) {
		printf("%s\n",
			strerror(errno));
		return 1;
	}

	create_input(data_in, o.test_l);

	//print_array(data_in, o.test_l);

	char ** prog_test_vector[] = {
		(char * [] ) {"./seq",    "-s", SHM_NAME, "-l", o.test_l_s, NULL},
		(char * [] ) {"./openmp", "-s", SHM_NAME, "-l", o.test_l_s, "-p", o.p, NULL},
		(char * [] ) {"./cilk",   "-s", SHM_NAME, "-l", o.test_l_s, "-p", o.p, NULL},
	};

	int i = o.run;
	//for(int i = 0; i < 3; i++) {
		for(int j = 0; j < o.r; j++) {
			DBG("Running %s\n", prog_test_vector[i][0]);
			pid_t p = fork();

			if(p == 0) {
				/* child */
				execv(prog_test_vector[i][0], prog_test_vector[i]);
			} else if(p > 0) {
				/* parent */
				/* TODO check return value of child, message accordingly */
				waitpid(p, (int *) NULL, 0);
				DBG("Test Finished\n");
			} else {
				printf("Error forking: %s\n",
					strerror(errno));
				return 1;
			}
		}
	//}

	if(0 > shm_unlink(SHM_NAME)) {
		printf("%s\n",
			strerror(errno));
		return 1;
	}
}
